/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "EnvironmentManager.h"
#include "SceneObject.h"
#include "NgUtil.h"

namespace PQEngine
{
	const Ogre::String EnvironmentManager::ENV_DATA_PATH="data/env";
	const Ogre::String EnvironmentManager::ENV_GROUP_NAME="group/env";

	EnvironmentManager::EnvironmentManager(SceneObject* sceneObject)
		: _sceneObject(sceneObject),_skyGradientsImage(0),_sunColorImage(0),
		_fogDensityMultiplier(1.0f),_fogColorMultiplier(1.0f),_starFieldImage(0),
		_skyDome(0),_sun(0),_moon(0),_groundFog(0),_clouds(0)
	{
		assert(_sceneObject);

		Ogre::LogManager::getSingleton().logMessage("Init Scene environment...");
		Ogre::LogManager::getSingleton().logMessage("EnvironmentManager* at "+Ogre::StringConverter::toString(reinterpret_cast<Ogre::uint>(this)));

		Ogre::SceneManager* sceneManager=_sceneObject->getSceneManager();
		Ogre::String uniqueId = Ogre::StringConverter::toString((unsigned int)this);
		_cameraNode=sceneManager->getRootSceneNode()->createChildSceneNode("Env/CameraNode/" + uniqueId);
		_groundNode=sceneManager->getRootSceneNode()->createChildSceneNode("Env/GroundNode/" + uniqueId);

		initEnvironment();
	}

	void EnvironmentManager::initEnvironment()
	{
		initResource();
		initAstroTimer();
		initSkyDome();
		initSun();
		initMoon();
		initClouds();
		initStarFeild();
		_sceneObject->getSceneManager()->setAmbientLight(Ogre::ColourValue());
		
	}

	void EnvironmentManager::initResource()
	{
		//load environment resource
		Ogre::ResourceGroupManager::getSingleton().addResourceLocation(ENV_DATA_PATH,"FileSystem",ENV_GROUP_NAME);
		Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup(ENV_GROUP_NAME);
	}

	void EnvironmentManager::initAstroTimer()
	{
		//control our environment by day time
		_astroTimer=new AstroTimer();

	}

	void EnvironmentManager::enableSkyDome(bool enabled)
	{
		if(_skyDome){
			_skyDome->enable(enabled);
		}
	}

	void EnvironmentManager::initSkyDome()
	{
		_skyDome=new SkyDome(_sceneObject);
		_skyDome->enable(false);

	}

	void EnvironmentManager::initSun()
	{
		_sun=new Sun(_sceneObject);
	}

	void EnvironmentManager::initMoon()
	{
		//_moon=new Moon(_sceneObject);
	}

	void EnvironmentManager::initClouds()
	{
		//_clouds=new CloudSystem(_sceneObject);
	}

	void EnvironmentManager::initStarFeild()
	{
		//_starField=new StarField(_sceneObject);
	}
	
	void EnvironmentManager::update(float deltaT)
	{
		_astroTimer->update(deltaT);
		double julianDay=_astroTimer->getJulianDay();
		double relDayTime=fmod(julianDay,1);
		float secondDiff=deltaT* _astroTimer->getTimeScale();

		// Get astronomical parameters.
		Ogre::Vector3 sunDir=getSunDirection(julianDay);
		Ogre::Vector3 moonDir=getMoonDirection(julianDay);

		float moonPhase=getMoonPhase(julianDay);

		 // Get parameters from sky colour model.
		float fogDensity=getFogDensity(relDayTime,sunDir);

		Ogre::ColourValue fogColor=getFogColor(relDayTime,sunDir);
		Ogre::ColourValue sunLightColor = getSunLightColor(relDayTime, sunDir);
		Ogre::ColourValue sunColor = getSunColor(relDayTime, sunDir);
		Ogre::ColourValue moonLightColor = getMoonLightColor(relDayTime, sunDir);
		Ogre::ColourValue moonBodyColor = getMoonBodyColor(moonDir); 

		fogDensity*=_fogDensityMultiplier;
		fogColor*=_fogColorMultiplier;

		// Update image starfield
		if(_starFieldImage){
			_starFieldImage->update(deltaT);
			_starFieldImage->setInclination(-getObserverLatitude());
		}

		if(_skyDome){
			_skyDome->setSunDirection(sunDir);
			_skyDome->setHazeColor(fogColor*_fogColorMultiplier);
			_skyDome->update(deltaT);
		}

		 // Update scene fog
		_sceneObject->getSceneManager()->setFog(Ogre::FOG_EXP2,fogColor*_fogColorMultiplier,fogDensity*_fogDensityMultiplier);

		if(_groundFog){
			_groundFog->setColor(fogColor*_groundFogMultiplier);
			_groundFog->setDensity(fogDensity*_groundDensityMultiplier);
		}

		if(_sun){
			_sun->update(sunDir,sunLightColor,sunColor);
		}

		if(_moon){
			_moon->update(moonDir,moonLightColor,moonBodyColor);
			_moon->setPhase(moonPhase);
		}
		
		if(_clouds){
			_clouds->update(secondDiff,sunDir,sunLightColor,fogColor,sunColor);
		}

		//Update ambient lighting.
		Ogre::ColourValue ambient=Ogre::ColourValue::Black;
		if(_moon){
			//ambient+=_moon->getLightColor()*_moon->getAmbientMultiplier();
			
		}

		/*

        // Update ambient lighting.
        if(getManageAmbientLight()) {
            Ogre::ColourValue ambient = Ogre::ColourValue::Black;
            if(getMoon()) {
                ambient += getMoon()->getLightColour() * getMoon()->getAmbientMultiplier();
            }
            if(getSun()) {
                ambient += getSun()->getLightColour() * getSun()->getAmbientMultiplier();
            }
            ambient.r = std::max(ambient.r, mMinimumAmbientLight.r);
            ambient.g = std::max(ambient.g, mMinimumAmbientLight.g);
            ambient.b = std::max(ambient.b, mMinimumAmbientLight.b);
            ambient.a = std::max(ambient.a, mMinimumAmbientLight.a);
            // Debug ambient factos(ick).
          
            mSceneMgr->setAmbientLight(ambient);
        }

        if(getSun() && getMoon()) {
            Ogre::Real moonBrightness = moonLightColour.r + moonLightColour.g + moonLightColour.b + moonLightColour.a;
            Ogre::Real sunBrightness = sunLightColour.r + sunLightColour.g + sunLightColour.b + sunLightColour.a;
            bool sunBrighterThanMoon =(sunBrightness > moonBrightness);

            if(getEnsureSingleLightSource()) {
                getMoon()->setForceDisable(sunBrighterThanMoon);
                getSun()->setForceDisable(!sunBrighterThanMoon);
            }
            if(getEnsureSingleShadowSource()) {
                getMoon()->getMainLight()->setCastShadows(!sunBrighterThanMoon);
                getSun()->getMainLight()->setCastShadows(sunBrighterThanMoon);
            }
        }
		*/

	}

	const Ogre::Vector3 EnvironmentManager::getSunDirection(double julianDay)
    {
        Ogre::Degree azimuth, altitude;
		Astronomy::getHorizontalSunPosition(julianDay,getObserverLongitude(), getObserverLatitude(),azimuth, altitude);
        Ogre::Vector3 ret = makeDirection(azimuth, altitude);

        return ret;
    }

	const Ogre::Vector3 EnvironmentManager::getMoonDirection(double julianDay)
    {
        Ogre::Degree azimuth, altitude;
		Astronomy::getHorizontalMoonPosition(julianDay,getObserverLongitude(), getObserverLatitude(),azimuth, altitude);
        Ogre::Vector3 ret = makeDirection(azimuth, altitude);

        return ret;
    }

	Ogre::Real EnvironmentManager::getMoonPhase(double julianDay)
	{
		 // Calculates julian days since January 22, 2008 13:36(full moon)
        // and divides by the time between lunations(synodic month)
        double T =(julianDay - 2454488.0665L) / 29.531026L;

        T = fabs(fmod(T, 1));
        return -fabs(-4 * T + 2) + 2;
	}

	Ogre::Real EnvironmentManager::getFogDensity(float time, const Ogre::Vector3 &sunDir)
    {
        if(!_skyGradientsImage) {
            return 0;
        }

		Ogre::Real elevation = sunDir.dotProduct(Ogre::Vector3::UNIT_Y) * 0.5 + 0.5;
        Ogre::ColourValue color = NgUtil::getInterpolatedColor(elevation, 1, _skyGradientsImage, false);
        return color.a;
    }

	Ogre::ColourValue EnvironmentManager::getFogColor(float time, const Ogre::Vector3 &sunDir)
    {
        if(!_skyGradientsImage) {
            return Ogre::ColourValue::Black;
        }

        Ogre::Real elevation = sunDir.dotProduct(Ogre::Vector3::UNIT_Y) * 0.5 + 0.5;
        Ogre::ColourValue color = NgUtil::getInterpolatedColor(elevation, 1, _skyGradientsImage, false);
        return color;
    }

	Ogre::ColourValue EnvironmentManager::getSunColor(float time, const Ogre::Vector3 &sunDir)
    {
        if(!_sunColorImage) {
            return Ogre::ColourValue::White;
        }

		Ogre::Real elevation = sunDir.dotProduct(Ogre::Vector3::UNIT_Y);
        elevation = elevation * 2 + 0.4;
        return NgUtil::getInterpolatedColor(elevation, 1, _sunColorImage, false);
    }

	Ogre::ColourValue EnvironmentManager::getSunLightColor(Ogre::Real time, const Ogre::Vector3 &sunDir)
    {
		if(!_skyGradientsImage){
            return Ogre::ColourValue::White;
        }
		Ogre::Real elevation = sunDir.dotProduct (Ogre::Vector3::UNIT_Y) * 0.5 + 0.5;

        // Hack: return averaged sky colours.
        // Don't use an alpha value for lights, this can cause nasty problems.
        Ogre::ColourValue color = NgUtil::getInterpolatedColor(elevation, elevation, _skyGradientsImage, false);
		Ogre::Real val = (color.r + color.g + color.b) / 3;
        color = Ogre::ColourValue(val, val, val, 1.0);
        return color;
    }

	Ogre::ColourValue EnvironmentManager::getMoonBodyColor(const Ogre::Vector3 &moonDir)
	{
        return Ogre::ColourValue::White;
    }

	Ogre::ColourValue EnvironmentManager::getMoonLightColor(float time, const Ogre::Vector3 &moonDir)
    {
		if(!_skyGradientsImage) {
            return Ogre::ColourValue::Blue;
        }
        // Scaled version of getSunLightColor
		Ogre::Real elevation = moonDir.dotProduct(Ogre::Vector3::UNIT_Y) * 0.5 + 0.5;
        Ogre::ColourValue color = NgUtil::getInterpolatedColor(elevation, elevation, _skyGradientsImage, false);
		Ogre::Real val =(color.r + color.g + color.b) / 3;
        color = Ogre::ColourValue(val / 2.5f, val / 2.5f, val / 2.5f, 1.0);
        return color;
    }

	const Ogre::Vector3 EnvironmentManager::makeDirection(Ogre::Degree azimuth, Ogre::Degree altitude)
    {
        Ogre::Vector3 ret;
        ret.z = -Ogre::Math::Cos(azimuth) * Ogre::Math::Cos(altitude);  // North 
        ret.x =  Ogre::Math::Sin(azimuth) * Ogre::Math::Cos(altitude);  // East
        ret.y = -Ogre::Math::Sin(altitude); // Zenith
        return ret;
    }


}
